﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace MetaSharp.Transformation.Patterns
{
    public class RulePattern : Pattern
    {
        public IImmutableArray<MetadataAttribute> Attributes { get; private set; }
        public Pattern Pattern { get; private set; }
        private PatternMatch _cached;

        public RulePattern(Pattern pattern, IImmutableArray<MetadataAttribute> attributes)
            : base(PatternKind.Rule)
        {
            this.Pattern = pattern;
            this.Attributes = attributes;
        }

        public override PatternMatch Compile()
        {
            lock(this)
            {
                if (_cached == null)
                {
                    lock(this)
                    {
                        var p = this.Pattern.Compile();
                        _cached = s => Target(s, p, this);
                    }
                }
            }

            return _cached;
        }

        private static async Task<IMatch> Target(IScope scope, PatternMatch pattern, RulePattern origin)
        {
            var memo = default(Memo);
            if (!scope.Memos.TryGet(new MemoKey(scope.Stream, origin), out memo))
            {
                var sub = scope.PushRule(pattern, origin);
                var match = await pattern(sub);

                if (match.IsLR)
                    match = await Grow(sub, pattern, origin);

                if (!match.Matched)
                    return await Transformation.Match.Fail(scope);

                return match.PopRule();
            }
            else
            {
                if (memo.Match.IsLR)
                {
                    if (scope.RuleStack.Count == 0)
                        throw new InvalidOperationException("Invalid recursion detected.");

                    if (scope.RuleStack.Peek().Rule != pattern)
                    {
                        throw new TransformationException(string.Format(
                            CultureInfo.CurrentCulture,
                            "Indirect left recursion detected in rule {0}.",
                            pattern.Method.ToString()));
                    }
                }

                var end = memo.Match.End.Stream;
                return memo.Match.SetEnd(scope.SetStream(end));
            }
        }

        private static async Task<IMatch> Grow(IScope scope, PatternMatch pattern, RulePattern origin)
        {
            var match = await Transformation.Match.Fail(scope);
            var start = scope.Stream;
            while (true)
            {
                scope = match
                    .End
                    .SetMemo(match, start, match.End.Stream, pattern, origin)
                    .SetStream(start);

                var result = await pattern(scope);
                if (!result.Matched ||
                    match.Matched &&
                    result.End.Stream.CompareTo(match.End.Stream) <= 0)
                    break;

                match = result;
            }

            return match;
        }
    }
}
